package org.xx.armory.spring5.jpa;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.BooleanSupplier;

import static org.xx.armory.commons.Validators.rejectIfNull;

public final class PredicateBuilder {
    private final List<Predicate> predicates;

    public PredicateBuilder() {
        this.predicates = new ArrayList<>();
    }

    public final PredicateBuilder addIfNotNull(
            Object value,
            Predicate predicate
    ) {
        rejectIfNull(predicate, "predicate");

        if (value != null) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder addIfNotEmpty(
            String value,
            Predicate predicate
    ) {
        rejectIfNull(predicate, "predicate");

        if (value != null && !value.isEmpty()) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder addIfNotEmpty(
            Collection<?> value,
            Predicate predicate
    ) {
        rejectIfNull(predicate, "predicate");

        if (value != null && !value.isEmpty()) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder addIfNotBlank(
            String value,
            Predicate predicate
    ) {
        rejectIfNull(predicate, "predicate");

        if (value != null && !value.isBlank()) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder addIfNull(
            Object value,
            Predicate predicate
    ) {
        rejectIfNull(predicate, "predicate");

        if (value == null) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder addIfEmpty(
            String value,
            Predicate predicate
    ) {
        rejectIfNull(predicate, "predicate");

        if (value == null || value.isEmpty()) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder addIfEmpty(
            Collection<?> value,
            Predicate predicate
    ) {
        if (value == null || value.isEmpty()) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder addIfBlank(
            String value,
            Predicate predicate
    ) {
        rejectIfNull(predicate, "predicate");

        if (value == null || value.isBlank()) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder addIfEquals(
            Object value,
            Object otherValue,
            Predicate predicate
    ) {
        rejectIfNull(predicate, "predicate");

        if (Objects.equals(value, otherValue)) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder addIfNotEquals(
            Object value,
            Object otherValue,
            Predicate predicate
    ) {
        rejectIfNull(predicate, "predicate");

        if (!Objects.equals(value, otherValue)) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder addIfIn(
            Object value,
            Collection<?> c,
            Predicate predicate
    ) {
        rejectIfNull(predicate, "predicate");

        if (c.contains(value)) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder addIfNotIn(
            Object value,
            Collection<?> c,
            Predicate predicate
    ) {
        rejectIfNull(predicate, "predicate");

        if (!c.contains(value)) {
            this.predicates.add(predicate);
        }

        return this;
    }


    public final PredicateBuilder add(
            BooleanSupplier pp,
            Predicate predicate
    ) {
        rejectIfNull(pp, "pp");
        rejectIfNull(predicate, "predicate");

        if (pp.getAsBoolean()) {
            this.predicates.add(predicate);
        }

        return this;
    }

    public final PredicateBuilder add(
            Predicate predicate
    ) {
        return add(() -> true, predicate);
    }

    public final <E> Predicate buildEqualAny(
            CriteriaBuilder cb,
            Expression<E> expression,
            Collection<? extends E> values
    ) {
        rejectIfNull(values, "values");

        return cb.or(values.stream().map(value -> cb.equal(expression, value)).toArray(Predicate[]::new));
    }

    public final Predicate build(
            CriteriaBuilder cb
    ) {
        return cb.and(this.predicates.toArray(Predicate[]::new));
    }

    public final Predicate buildOr(
            CriteriaBuilder cb
    ) {
        return cb.or(this.predicates.toArray(Predicate[]::new));
    }
}
